using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Diagnostics;
using Microsoft.BizTalk.Message.Interop;
using Microsoft.XLANGs.BaseTypes;

namespace Microsoft.Samples.BizTalk.Adapter.Tcp.Send
{
    /// <summary>
    ///     This class encapsulates a send location used to transmit messages.
    /// </summary>
    /// <author>
    ///     Paul Brown
    /// </author>
    public class SendLocation
    {
        #region Public Static Members

        // The context property for outbound transport location
        public static readonly PropertyBase OutboundTransportLocationProperty = new BTS.OutboundTransportLocation();

        // The BizTalk Party properties
        public static readonly string BizTalkPartyAliasName = TraceHelper.ResourceManager.GetString(ResourceConstants.BizTalkPartyAliasName);
        public static readonly string BizTalkPartyAliasQualifierDnsName = TraceHelper.ResourceManager.GetString(ResourceConstants.BizTalkPartyAliasQualifierDnsName);
        public static readonly string BizTalkPartyAliasQualifierIpAddress = TraceHelper.ResourceManager.GetString(ResourceConstants.BizTalkPartyAliasQualifierIpAddress);
        public static readonly string BizTalkPartyAliasQualifierDuplexAddress = TraceHelper.ResourceManager.GetString(ResourceConstants.BizTalkPartyAliasQualifierDuplexAddress);

        #endregion

        #region Private Instance Members

        private SendAdapter         _adapter;               // The adapter
        private IBaseMessage        _message;               // The message associated with the transmission
        private string              _uri;                   // The TCP address
        private string              _tcpEndpointAddress;    // The IP address or DNS name of the endpoint to connect to
        private int                 _tcpEndpointPort;       // The port of the endpoint to connect to
        private bool                _tcpSupportsDuplex;     // Specifies whether this send location supports duplex sending
        private string              _tcpPartyEndpoint;      // Specifies the BizTalk Party name if this send location supports duplex sending
        private int                 _socketReceiveBufferSize; // The socket receive buffer size in bytes
        private int                 _socketReceiveTimeout;  // The socket receive timeout in milliseconds
        private int                 _socketSendBufferSize;  // The socket send buffer size in bytes
        private int                 _socketSendTimeout;     // The socket send timeout in milliseconds
        private int                 _receiveDataBufferSize; // The size of the data buffer used to pass to the socket for receiving
        private int                 _sendDataBufferSize;    // The size of the data buffer used to pass to the socket for sending
        private bool                _isTwoWay;              // Is the send location set up as a solicit-response message exchange pattern
        private int                 _closeTimeout;          // The timeout in seconds when closing the connection
        private Encoding            _dataEncoding;          // The encoding of the data being received
        private byte[]              _frameStart;            // The message start frame
        private byte[]              _frameEnd;              // The message end frame
        private bool                _isOrdered;             // Has the port been marked for ordered delivery?

        #endregion

        #region Constructor

        /// <summary>
        ///     This constructor initializes the instance by parsing the message of its properties.
        /// </summary>
        public SendLocation(SendAdapter adapter, IBaseMessage message)
        {
            // Validate
            if (adapter == null) throw new ArgumentNullException("adapter");
            if (message == null) throw new ArgumentNullException("message");

            // Initialize
            this._adapter = adapter;
            this._message = message;
            this._uri = null;
            this._tcpEndpointAddress = null;
            this._tcpEndpointPort = 0;
            this._tcpSupportsDuplex = false;
            this._tcpPartyEndpoint = null;
            this._socketReceiveBufferSize = 8192;
            this._socketReceiveTimeout = 500;
            this._socketSendBufferSize = 8192;
            this._socketSendTimeout = 500;
            this._receiveDataBufferSize = 2048;
            this._sendDataBufferSize = 2048;
            this._isTwoWay = false;
            this._closeTimeout = 0;
            this._dataEncoding = null;
            this._frameStart = null;
            this._frameEnd = null;
            this._isOrdered = false;

            // Parse message context and set up the state of this instance
            this.ParseMessage();
        }

        #endregion

        #region Public Instance Properties

        /// <summary>
        ///     This property returns a reference to the adapter.
        /// </summary>
        public SendAdapter Adapter
        {
            get { return (this._adapter); }
        }

        /// <summary>
        ///     This property returns the message to be transmitted.
        /// </summary>
        public IBaseMessage Message
        {
            get { return (this._message); }
        }

        /// <summary>
        ///     This property returns the address of the send location.
        /// </summary>
        public string Uri
        {
            get { return (this._uri); }
        }

        /// <summary>
        ///     This property returns the close timeout in seconds when closing the socket.
        /// </summary>
        public int CloseTimeout
        {
            get { return (this._closeTimeout); }
        }

        /// <summary>
        ///     This property gets or sets the TCP address (IP address or host name) of the remote endpoint.
        /// </summary>
        public string EndpointAddress
        {
            get { return (this._tcpEndpointAddress); }
            set { this._tcpEndpointAddress = value; }
        }

        /// <summary>
        ///     This property gets or sets the TCP port of the remote endpoint.
        /// </summary>
        public int EndpointPort
        {
            get { return (this._tcpEndpointPort); }
            set { this._tcpEndpointPort = value; }
        }

        /// <summary>
        ///     This property returns whether the send location supports sending to a duplex connection rather than a remote
        ///     endpoint.
        /// </summary>
        public bool SupportsDuplex
        {
            get { return (this._tcpSupportsDuplex); }
        }

        /// <summary>
        ///     This property returns the name of the BizTalk Party that is used to resolve a duplex listener address when this
        ///     send location is supporting sending to duplex connections.
        /// </summary>
        public string PartyEndpoint
        {
            get { return (this._tcpPartyEndpoint); }
        }

        /// <summary>
        ///     This property returns the receive buffer size in bytes for the underlying socket.
        /// </summary>
        public int SocketReceiveBufferSize
        {
            get { return (this._socketReceiveBufferSize); }
        }

        /// <summary>
        ///     This property returns the receive timeout in milliseconds for receive operations on the socket.
        /// </summary>
        public int SocketReceiveTimeout
        {
            get { return (this._socketReceiveTimeout); }
        }

        /// <summary>
        ///     This property returns the send buffer size in bytes for the underlying socket.
        /// </summary>
        public int SocketSendBufferSize
        {
            get { return (this._socketSendBufferSize); }
        }

        /// <summary>
        ///     This property returns the send timeout in milliseconds for send operations on the socket.
        /// </summary>
        public int SocketSendTimeout
        {
            get { return (this._socketSendTimeout); }
        }

        /// <summary>
        ///     This property returns the receive data buffer in bytes when receiving data from the socket.
        /// </summary>
        public int ReceiveDataBufferSize
        {
            get { return (this._receiveDataBufferSize); }
        }

        /// <summary>
        ///     This property returns the send data buffer in bytes when sending data to the socket.
        /// </summary>
        public int SendDataBufferSize
        {
            get { return (this._sendDataBufferSize); }
        }

        /// <summary>
        ///     This property returns the encoding used for the messages.
        /// </summary>
        public Encoding DataEncoding
        {
            get { return (this._dataEncoding); }
        }

        /// <summary>
        ///     This property returns the delimiter byte sequence used as the start frame.
        /// </summary>
        public byte[] FrameStart
        {
            get { return (this._frameStart); }
        }

        /// <summary>
        ///     This property returns the delimiter byte sequence used as the end frame.
        /// </summary>
        public byte[] FrameEnd
        {
            get { return (this._frameEnd); }
        }

        /// <summary>
        ///     This property returns whether this send location should operate in a solicit-response pattern.
        /// </summary>
        public bool IsTwoWay
        {
            get { return (this._isTwoWay); }
        }

        /// <summary>
        ///     This property determines whether the send location has been marked for ordered delivery.
        /// </summary>
        public bool IsOrdered
        {
            get { return (this._isOrdered); }
        }

        #endregion

        #region Private Instance Methods

        /// <summary>
        ///     This method parses the message and sets up the properties of this send location.
        /// </summary>
        private void ParseMessage()
        {
            // Get outbound transport location
            this._uri = (string)this.Message.Context.Read(SendLocation.OutboundTransportLocationProperty.Name.Name,
                                                          SendLocation.OutboundTransportLocationProperty.Name.Namespace);

            // Get configuration for send location
            XmlDocument locationConfiguration = ConfigurationHelper.LoadAdapterConfiguration(this.Adapter, this.Message);

            if (locationConfiguration == null)
            {
                if (this.Adapter.TraceSwitch.TraceVerbose)
                    TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendLocationIsDynamic);

                // Dynamic send port
                this.ParseDynamicPortUri(this.Uri);
            }
            else
            {
                if (this.Adapter.TraceSwitch.TraceVerbose)
                    TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendLocationIsStatic);

                // Static send port
                this.LoadSendLocationConfiguration(locationConfiguration);
            }

            // A duplex send location does not support a solicit response exchange pattern
            if (this.SupportsDuplex && this.IsTwoWay)
            {
                string message = TraceHelper.FormatEntry(ResourceConstants.SendTwoWayPortAndDuplexNotSupported, this.Uri);
                TraceHelper.WriteRawEntry(TraceLevel.Error, message);
                throw new Exception(message);
            }
        }

        /// <summary>
        ///     This method parses the URI to determine the address for the send location.  The rest of the send location
        ///     properties are defaulted within the constructor (should be in the send handler properties).
        /// </summary>
        private void ParseDynamicPortUri(string uri)
        {
            // Load into a URI instance
            Uri address = new Uri(uri);

            // Check to see whether there is a port number in the URI
            if (address.Port != -1)
            {
                // Treat as a non-duplex send location
                this._tcpSupportsDuplex = false;
                this._tcpEndpointAddress = address.Host;
                this._tcpEndpointPort = address.Port;

                if (this.Adapter.TraceSwitch.TraceVerbose)
                    TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendNonDuplexDynamicPort, this.Uri, this.EndpointAddress, this.EndpointPort);
            }
            else
            {
                // Treat as a duplex supported send location
                this._tcpSupportsDuplex = true;
                this._tcpPartyEndpoint = address.Host;

                if (this.Adapter.TraceSwitch.TraceVerbose)
                    TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendDuplexDynamicPort, this.Uri, this.PartyEndpoint);
            }
        }

        /// <summary>
        ///     This method loads the configuration for a specific send location.
        /// </summary>
        private void LoadSendLocationConfiguration(XmlDocument locationConfiguration)
        {
            // TCP Endpoint Address
            XmlNode node = ConfigurationHelper.GetEntry(locationConfiguration, "/Config/tcpEndpointAddress", false);
            this._tcpEndpointAddress = (node == null ? string.Empty : node.InnerText);
            if (this.Adapter.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendEndpointAddress, this.Uri, this.EndpointAddress);

            // TCP Endpoint Port
            node = ConfigurationHelper.GetEntry(locationConfiguration, "/Config/tcpEndpointPort");
            this._tcpEndpointPort = int.Parse(node.InnerText);
            if (this.Adapter.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendEndpointPort, this.Uri, this.EndpointPort);

            // TCP Close Timeout
            node = ConfigurationHelper.GetEntry(locationConfiguration, "/Config/tcpCloseTimeout");
            this._closeTimeout = int.Parse(node.InnerText);
            if (this.Adapter.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendCloseTimeout, this.Uri, this.CloseTimeout);

            // TCP Socket Receive Buffer Size
            node = ConfigurationHelper.GetEntry(locationConfiguration, "/Config/tcpSocketReceiveBufferSize");
            this._socketReceiveBufferSize = int.Parse(node.InnerText);
            if (this.Adapter.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendSocketReceiveBufferSize, this.Uri, this.SocketReceiveBufferSize);

            // TCP Socket Receive Timeout
            node = ConfigurationHelper.GetEntry(locationConfiguration, "/Config/tcpSocketReceiveTimeout");
            this._socketReceiveTimeout = int.Parse(node.InnerText);
            if (this.Adapter.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendSocketReceiveTimeout, this.Uri, this.SocketReceiveTimeout);

            // TCP Socket Send Buffer Size
            node = ConfigurationHelper.GetEntry(locationConfiguration, "/Config/tcpSocketSendBufferSize");
            this._socketSendBufferSize = int.Parse(node.InnerText);
            if (this.Adapter.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendSocketSendBufferSize, this.Uri, this.SocketSendBufferSize);

            // TCP Socket Send Timeout
            node = ConfigurationHelper.GetEntry(locationConfiguration, "/Config/tcpSocketSendTimeout");
            this._socketSendTimeout = int.Parse(node.InnerText);
            if (this.Adapter.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendSocketSendTimeout, this.Uri, this.SocketSendTimeout);

            // TCP Supports Duplex
            node = ConfigurationHelper.GetEntry(locationConfiguration, "/Config/tcpSupportsDuplex");
            this._tcpSupportsDuplex = bool.Parse(node.InnerText);
            if (this.Adapter.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendSupportsDuplex, this.Uri, this.SupportsDuplex);

            // TCP Party Endpoint
            if (this.SupportsDuplex)
            {
                node = ConfigurationHelper.GetEntry(locationConfiguration, "/Config/tcpPartyEndpoint");
                this._tcpPartyEndpoint = (node == null ? string.Empty : node.InnerText);
                if (this.Adapter.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendPartyEndpoint, this.Uri, this.PartyEndpoint);
            }

            // Receive Data Buffer Size
            node = ConfigurationHelper.GetEntry(locationConfiguration, "/Config/receiveBufferSize");
            this._receiveDataBufferSize = int.Parse(node.InnerText);
            if (this.Adapter.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendReceiveDataBufferSize, this.Uri, this.ReceiveDataBufferSize);

            // Send Data Buffer Size
            node = ConfigurationHelper.GetEntry(locationConfiguration, "/Config/sendBufferSize");
            this._sendDataBufferSize = int.Parse(node.InnerText);
            if (this.Adapter.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendSendDataBufferSize, this.Uri, this.SendDataBufferSize);

            // Data Output Byte Order Mark
            node = ConfigurationHelper.GetEntry(locationConfiguration, "/Config/dataOutputBom");
            bool outputBom = bool.Parse(node.InnerText);
            if (this.Adapter.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendDataOutputByteOrderMark, this.Uri, outputBom);

            // Data Byte Order
            node = ConfigurationHelper.GetEntry(locationConfiguration, "/Config/dataByteOrder");
            EndianTypes byteOrder = (EndianTypes)Enum.Parse(typeof(EndianTypes), node.InnerText);
            if (this.Adapter.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendDataByteOrder, this.Uri, byteOrder);

            // Data Custom Encoding
            node = ConfigurationHelper.GetEntry(locationConfiguration, "/Config/dataEncodingCustomValue", false);
            string customEncodingValue = (node == null ? string.Empty : node.InnerText);
            if (this.Adapter.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendDataCustomEncoding, this.Uri, customEncodingValue);

            // Data Encoding
            node = ConfigurationHelper.GetEntry(locationConfiguration, "/Config/dataEncoding");
            this._dataEncoding = ConfigurationHelper.GetEncoding(node, customEncodingValue, byteOrder, outputBom);
            if (this.Adapter.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendDataEncoding, this.Uri, this.DataEncoding.ToString());

            // Frame Output Byte Order Mark
            node = ConfigurationHelper.GetEntry(locationConfiguration, "/Config/frameOutputBom");
            outputBom = bool.Parse(node.InnerText);
            if (this.Adapter.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendFrameOutputByteOrderMark, this.Uri, outputBom);

            // Frame Byte Order
            node = ConfigurationHelper.GetEntry(locationConfiguration, "/Config/frameByteOrder");
            byteOrder = (EndianTypes)Enum.Parse(typeof(EndianTypes), node.InnerText);
            if (this.Adapter.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendFrameByteOrder, this.Uri, byteOrder);

            // Frame Custom Encoding
            node = ConfigurationHelper.GetEntry(locationConfiguration, "/Config/frameEncodingCustomValue", false);
            customEncodingValue = (node == null ? string.Empty : node.InnerText);
            if (this.Adapter.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendFrameCustomEncoding, this.Uri, customEncodingValue);

            // Frame Encoding
            node = ConfigurationHelper.GetEntry(locationConfiguration, "/Config/frameEncoding");
            Encoding frameEncoding = ConfigurationHelper.GetEncoding(node, customEncodingValue, byteOrder, outputBom);
            if (this.Adapter.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendFrameEncoding, this.Uri, (frameEncoding == null ? TraceHelper.ResourceManager.GetString(ResourceConstants.SendBinaryEncoding) : frameEncoding.ToString()));

            // Frame Start
            node = ConfigurationHelper.GetEntry(locationConfiguration, "/Config/frameStartDelimiter");
            if (frameEncoding == null)
            {
                // Binary
                string[] hexValues = node.InnerText.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                this._frameStart = new byte[hexValues.Length];
                for (int i = 0; i < hexValues.Length; i++)
                {
                    this._frameStart[i] = Convert.ToByte(hexValues[i], 16);
                }
            }
            else
            {
                // Text
                this._frameStart = frameEncoding.GetBytes(node.InnerText);
            }
            if (this.Adapter.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendFrameStart, this.Uri, node.InnerText);

            // Frame End
            node = ConfigurationHelper.GetEntry(locationConfiguration, "/Config/frameEndDelimiter");
            if (frameEncoding == null)
            {
                // Binary
                string[] hexValues = node.InnerText.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                this._frameEnd = new byte[hexValues.Length];
                for (int i = 0; i < hexValues.Length; i++)
                {
                    this._frameEnd[i] = Convert.ToByte(hexValues[i], 16);
                }
            }
            else
            {
                // Text
                this._frameEnd = frameEncoding.GetBytes(node.InnerText);
            }
            if (this.Adapter.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendFrameEnd, this.Uri, node.InnerText);

            // Two Way Send Port
            PropertyBase twoWayProperty = new global::BTS.IsSolicitResponse();
            object twoWay = this.Message.Context.Read(twoWayProperty.Name.Name, twoWayProperty.Name.Namespace);

            if (twoWay == null)
            {
                string message = TraceHelper.FormatEntry(ResourceConstants.ConfigurationPropertyMissing, twoWayProperty.Name.Name);
                TraceHelper.WriteRawEntry(TraceLevel.Error, message);
                throw new AdapterConfigurationException(message);
            }
            else
            {
                this._isTwoWay = (bool)twoWay;
            }
            if (this.Adapter.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendTwoWaySendPort, this.Uri, this.IsTwoWay);

            // Ordered Delivery
            //object isOrdered = this.Message.Context.Read(twoWayProperty.Name.Name, twoWayProperty.Name.Namespace);

            //if (isOrdered == null)
            //{
            //    string message = TraceHelper.FormatEntry(ResourceConstants.ConfigurationPropertyMissing, "");
            //    TraceHelper.WriteRawEntry(TraceLevel.Error, message);
            //    throw new AdapterConfigurationException(message);
            //}
            //else
            //{
            //    this._isOrdered = (bool)isOrdered;
            //}
            //if (this.Adapter.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendIsOrdered, this.Uri, this.IsOrdered);
        }

        #endregion
    }
}
